package org.glavo.bot.data

import org.glavo.bot.NamePattern
import java.util.regex.Pattern

object Death {
    private val splitter = Regex("((?<=%[1-3]\\\$s)|(?=%[1-3]\\\$s))")
    private val specialChars = Regex("""[*+|\[\](){}]""")

    private val asciiRegex = Regex("^[a-zA-Z0-9-_ ](.*[a-zA-Z0-9-_ ])?$")

    private fun needSpace(ch: Char): Boolean =
        ch in 'a'..'z'
                || ch in 'A'..'Z'
                || ch in '0'..'9'
                || ch == '-'
                || ch == '_'

    private fun escape(str: String): String {
        return str.replace(specialChars) {
            "\\" + it.value
        }
    }

    private fun playerName(str: String): String {
        val trim = str.trim()
        return if (trim.lastOrNull()?.let { needSpace(it) } != false) "$trim " else trim
    }
    private fun killerName(str: String): String {
        val trim = str.trim()
        val zh = entriesTranslator.getOrDefault(trim, trim)

        return when {
            zh.isEmpty() -> ""
            needSpace(zh.first()) || needSpace(zh.last()) -> " $zh "
            else -> zh
        }
    }

    private fun itemName(str: String): String {
        val trim = str.trim()
        val zh = Minecraft.lang.entries.find {
            (it.key.startsWith("item.minecraft.") || it.key.startsWith("block.minecraft.")) && it.value == trim
        }?.key?.let { Minecraft.langZh[it] } ?: trim

        return when {
            zh.isEmpty() -> ""
            needSpace(zh.first()) || needSpace(zh.last()) -> " $zh "
            else -> zh
        }
    }

    private fun readEntriesTranslator(): Map<String, String> {
        val res = mutableMapOf<String, String>()
        for ((key, value) in Minecraft.lang) {
            if (key.startsWith("entity.minecraft.")) {
                val zh = Minecraft.langZh[key]
                if (zh != null) {
                    res[value] = zh
                }
            }
        }
        return res
    }

    @Volatile
    private var entriesTranslator: Map<String, String> = readEntriesTranslator()

    private fun readDeathMessages(): List<(String) -> String?> {
        val itemList = Minecraft.items.joinToString("|", postfix = "|\\[.*]") { escape(it.displayName) }

        val playerToken = "(?<player>$NamePattern)"
        val killerToken = "(?<killer>[^ ]+( (?!(using|with) ($itemList|magic)\$).+)?)"
        val itemToken = "(?<item>$itemList)"

        val playerPlaceholder = "%1\$s"
        val killerPlaceholder = "%2\$s"
        val itemPlaceholder = "%3\$s"

        return Minecraft.lang.mapNotNull { (key, value) ->
            if (!key.startsWith("death.")) {
                return@mapNotNull null
            }

            val zh = Minecraft.langZh[key] ?: value

            val playerPlaceholderIdx = value.indexOf(playerPlaceholder)
            val killerPlaceholderIdx = value.indexOf(killerPlaceholder)
            val itemPlaceholderIdx = value.indexOf(itemPlaceholder)

            if (playerPlaceholderIdx < 0) {
                return@mapNotNull { input: String -> if (input == value) zh else null }
            }

            val spited = value.split(splitter)

            if (killerPlaceholderIdx < 0) {
                val regex = Pattern.compile(buildString {
                    append('^')
                    for (it in spited) {
                        when (it) {
                            playerPlaceholder -> append(playerToken)
                            else -> append(it)
                        }
                    }
                    append('$')
                })

                return@mapNotNull { input: String ->
                    val matcher = regex.matcher(input)
                    if (matcher.matches()) zh.format(playerName(matcher.group("player"))) else null
                }
            }

            if (itemPlaceholderIdx < 0) {
                val regex = Pattern.compile(buildString {
                    append('^')
                    for (it in spited) {
                        when (it) {
                            playerPlaceholder -> append(playerToken)
                            killerPlaceholder -> append(killerToken)
                            else -> append(it)
                        }
                    }
                    append('$')
                })

                return@mapNotNull { input: String ->
                    val matcher = regex.matcher(input)
                    if (matcher.matches()) zh.format(
                        playerName(matcher.group("player")),
                        killerName(matcher.group("killer"))
                    ) else null
                }
            }

            val regex = Pattern.compile(buildString {
                append('^')
                for (it in spited) {
                    when (it) {
                        playerPlaceholder -> append(playerToken)
                        killerPlaceholder -> append(killerToken)
                        itemPlaceholder -> append(itemToken)
                        else -> append(it)
                    }
                }
                append('$')
            })

            return@mapNotNull { input: String ->
                val matcher = regex.matcher(input)
                if (matcher.matches()) zh.format(
                    playerName(matcher.group("player")),
                    killerName(matcher.group("killer")),
                    itemName(matcher.group("item"))
                ) else null
            }
        }.reversed()
    }

    @Volatile
    var deathMessages = readDeathMessages()
        private set

    fun reload() {
        entriesTranslator = readEntriesTranslator()
        deathMessages = readDeathMessages()
    }

    fun match(message: String): String? {
        for (deathMessage in deathMessages) {
            deathMessage(message).let { if (it != null) return it }
        }
        return null
    }
}